﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using Newtonsoft.Json;
using System.IO.Pipes;
using Main.Packets;
using System.Threading;

namespace Main.PrimitivesCommunication
{
    /// <summary>
    /// Primitive de communication entre couche transport et couche réseau
    /// </summary>
    public class Primitive
    {
        #region Attributs

        private PrimitiveTypes primitiveType;

        private byte idConnexion;
        private byte adresseSource;
        private byte adresseDestination;
        private byte adresseReponse;

        private string donneesUtilisateur;

        private Raisons raison;

        #endregion

        #region Constructeurs
        
        private Primitive() { }

        private Primitive(PrimitiveTypes primitive, byte idConnexion, byte adresseSource, byte adresseDestination, byte adresseReponse)
        {
            this.idConnexion = idConnexion;
            this.primitiveType = primitive;
            this.adresseSource = adresseSource;
            this.adresseDestination = adresseDestination;
            this.adresseReponse = adresseReponse;
        }

        private Primitive(PrimitiveTypes primitive, byte idConnexion, string donneesUtilisateur)
        {
            this.idConnexion = idConnexion;
            this.primitiveType = primitive;
            this.donneesUtilisateur = donneesUtilisateur;
        }

        private Primitive(PrimitiveTypes primitive, byte adresseReponse)
        {
            this.primitiveType = primitive;
            this.adresseReponse = adresseReponse;
        }

        private Primitive(PrimitiveTypes primitive, byte adresseReponse, Raisons raison)
        {
            this.primitiveType = primitive;
            this.adresseReponse = adresseReponse;
            this.raison = raison;
        }

        #endregion

        #region Propriétés

        public Int16 Size
        {
            get
            {
                Int16 retour;
                switch(primitiveType)
                {
                    case PrimitivesCommunication.PrimitiveTypes.NConnectConfirmation:
                    case PrimitivesCommunication.PrimitiveTypes.NConnectResponse:
                    case PrimitivesCommunication.PrimitiveTypes.NDisconnectRequest:
                        retour = 1;
                        break;
                    case PrimitivesCommunication.PrimitiveTypes.NConnectRequest:
                    case PrimitivesCommunication.PrimitiveTypes.NConnectIndication:
                    case PrimitivesCommunication.PrimitiveTypes.NDisconnectIndication:
                        retour = 2;
                        break;
                    case PrimitivesCommunication.PrimitiveTypes.NDataRequest:
                    case PrimitivesCommunication.PrimitiveTypes.NDataIndication:
                        retour = 16;
                        break;
                    default:
                        throw new NotImplementedException();
                }
                return retour;
            }
        }

        public byte IdConnexion { get { return idConnexion; } set { idConnexion = value; } }
        public byte AdresseSource { get { return adresseSource; } set { adresseSource = value; } }
        public byte AdresseDestination { get { return adresseDestination; } set { adresseDestination = value; } }
        public byte AdresseReponse { get { return adresseReponse; } set { adresseReponse = value; } }

        public string DonneesUtilisateur { get { return donneesUtilisateur; } set { donneesUtilisateur = value; } }

        public Raisons Raison { get { return raison; } set { raison = value; } }

        public PrimitiveTypes PrimitiveType { get { return primitiveType; } set { primitiveType = value; } }

        #endregion

        #region Méthodes de création des primitives

        /// <summary>
        /// Instanciation d'une primitive NConnectRequest
        /// </summary>
        static public Primitive CreateNConnectReq(byte idConnexion, byte adresseSource, byte adresseDestination)
        {
            return new Primitive(PrimitiveTypes.NConnectRequest, idConnexion, adresseSource, adresseDestination, 0);
        }

        /// <summary>
        /// Instanciation d'une primitive NConnectIndication (Jamais utilisé puisque le système b est simulé)
        /// </summary>
        static public Primitive CreateNConnectInd(byte idConnexion, byte adresseSource, byte adresseDestination)
        {
            return new Primitive(PrimitiveTypes.NConnectIndication, idConnexion, adresseSource, adresseDestination, 0);
        }

        /// <summary>
        /// Instanciation d'une primitive NConnectResponse (Jamais utilisé puisque le système b est simulé)
        /// </summary>
        static public Primitive CreateNConnectResp(byte adresseReponse)
        {
            return new Primitive(PrimitiveTypes.NConnectResponse, adresseReponse);
        }

        /// <summary>
        /// Instanciation d'une primitive NConnectConfirmation
        /// </summary>
        static public Primitive CreateNConnectConf(byte adresseReponse)
        {
            return new Primitive(PrimitiveTypes.NConnectConfirmation, adresseReponse);
        }

        /// <summary>
        /// Instanciation d'une primitive NDataRequest
        /// </summary>
        static public Primitive CreateNDataReq(byte idConnexion, string donneesUtilisateur)
        {
            return new Primitive(PrimitiveTypes.NDataRequest, idConnexion, donneesUtilisateur);
        }

        /// <summary>
        /// Instanciation d'une primitive NDataIndication (Jamais utilisé puisque le système b est simulé)
        /// </summary>
        static public Primitive CreateNDataInd(byte idConnexion, string donneesUtilisateur)
        {
            return new Primitive(PrimitiveTypes.NDataIndication, idConnexion, donneesUtilisateur);
        }

        /// <summary>
        /// Instanciation d'une primitive NDisconnectRequest
        /// </summary>
        static public Primitive CreateNDisconnectReq(byte adresseReponse)
        {
            return new Primitive(PrimitiveTypes.NDisconnectRequest, adresseReponse);
        }

        /// <summary>
        /// Instanciation d'une primitive NDisconnectIndication
        /// </summary>
        static public Primitive CreateNDisconnectInd(byte adresseReponse, Raisons raison)
        {
            return new Primitive(PrimitiveTypes.NDisconnectIndication, adresseReponse, raison);
        }

        #endregion

        #region Méthodes d'envoie et de réception des primitives entre les couches.

        /// <summary>
        /// Envoie la primitive dans le flux passé en paramètre
        /// </summary>
        /// <param name="sw"></param>
        public void Send(StreamWriter sw)
        {
            sw.WriteLine(JsonConvert.SerializeObject(this));
        }

        /// <summary>
        /// Instancie une primitive à partir d'un flux passé en paramètre
        /// </summary>
        /// <param name="sr"></param>
        /// <returns></returns>
        public static Primitive Receive(StreamReader sr)
        {
            Primitive p = null;
            string str = sr.ReadLine();
            if(!String.IsNullOrEmpty(str))
                p = JsonConvert.DeserializeObject<Primitive>(str) as Primitive;
            return p;
        }

        /// <summary>
        /// Instancie une primitive à partir d'une ligne reçu d'une couche supérieure ou inférieure
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static Primitive Receive(string str)
        {
            return JsonConvert.DeserializeObject<Primitive>(str) as Primitive;
        }

        #endregion

        public override string ToString()
        {
            string toString = Enum.GetName(typeof(PrimitiveTypes), primitiveType);

            if (this.primitiveType == PrimitiveTypes.NConnectRequest || this.primitiveType == PrimitiveTypes.NConnectIndication)
                toString += (" Source : " + this.adresseSource + " Destination : " + this.adresseDestination);
            else if (this.primitiveType == PrimitiveTypes.NConnectResponse || this.primitiveType == PrimitiveTypes.NConnectConfirmation || this.primitiveType == PrimitiveTypes.NDisconnectRequest)
                toString += (" Adresse reponse : " + this.adresseReponse);
            else if (this.primitiveType == PrimitiveTypes.NDisconnectIndication)
                toString += (" Adresse reponse : " + this.adresseReponse + " Raison : " + this.raison);
            else if (this.primitiveType == PrimitiveTypes.NConnectResponse || this.primitiveType == PrimitiveTypes.NConnectConfirmation || this.primitiveType == PrimitiveTypes.NDataRequest)
                toString += (" Données utilisateurs : " + this.donneesUtilisateur);

            return toString;
        }
    }
}
